package com.rengwuxian.coursecoroutines._4_flow

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.retry
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeoutException
import kotlin.coroutines.EmptyCoroutineContext

/**
 * retry()和retryWhen()操作符
 * Flow的异常、catch()、重试
 *
 * retry()和retryWhen()也是针对上游Flow的操作符。和catch()的核心原理是相通的。
 * 是在上游Flow抛异常的时候重启上游Flow，是提供重试功能的。也是一种接管（插了一根侧管）。
 * 也可以在某些条件下不重启，让异常往下抛。
 * retry()：默认情况下是无限重启，直到上游Flow正常结束。
 * 下游不知道这些动作，无感。
 *
 * 如果retry之前还有操作符，比如map{}，那么retry会重新启动在自己前面的整个上游Flow链条，插到自己头顶上，跟下游共同重新拼成一个完整的链条。
 * 第一个参数是重试的次数，第二个参数是一个函数，参数是上游抛的异常，返回值是一个Boolean类型，用于判断是否需要重试。
 * 两个参数都满足，才会继续重试，否则会继续往下抛异常。
 *
 * retryWhen { cause, attempt ->  }是retry的变种，第一个参数是异常，第二个参数是重试过的次数，第一次到来时，该值为0.
 */
fun main() = runBlocking<Unit> {
  val scope = CoroutineScope(EmptyCoroutineContext)
  val flow1 = flow {
    for (i in 1..5) {
      // 数据库读数据
      // 网络请求
      if (i == 3) {
        throw RuntimeException("flow() error")
      } else {
        emit(i)
      }
    }
  }.map { it * 2 }.retry(3) {
    it is RuntimeException
  }/*.retryWhen { cause, attempt ->  }*/
  scope.launch {
    try {
      flow1.collect {
        println("Data: $it")
      }
    } catch (e: TimeoutException) {
      println("Network error: $e")
    } catch (e: RuntimeException) {
      println("RuntimeException: $e")
    }
  }
  delay(10000)
}